package cn.blnp.net.flowable.ext.child.inline;

import cn.hutool.core.map.MapUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.filter.SimplePropertyPreFilter;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.*;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ExecutionQuery;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.job.api.Job;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <h3>子流程-内嵌子流程</h3>
 *
 * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
 * @version 1.0
 * @since 2025/5/20 9:00
 */
@Slf4j
@SpringBootTest
public class InlineChildFlowTest {

    @Resource
    private RepositoryService repositoryService;
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private TaskService taskService;
    @Resource
    private HistoryService historyService;
    @Resource
    private ManagementService managementService;

    /**
     *  <h2>内嵌子流程</h2>
     *  <hr/>
     *  <p>
     *  &emsp;&emsp;内嵌子流程又称为嵌入式子流程，是一个可以包含其他活动、分支、事件等的活动。通常意义上的子流程指的就是内嵌子流程，表现为将一个流程（子流程）
     * 定义在另一个流程（主流程）的内部。子流程作为主流程的一部分，是主流程中的流程片段，并非独立的流程定义，一般作为局部通用逻辑处理。根据特定业
     * 务需要，使用子流程可以使复杂的单个主流程设计清晰直观。
     *  </p>
     *  <p>
     *  &emsp;&emsp;内嵌子流程就是将其中一部分可复用的片段组合到一个区域块中进行复用，将整个子流程完整地定义在主流程中。内嵌子流程支持子流程的展开与折叠，使流
     * 程图设计更加简洁明了。如果不使用内嵌子流程，同样也可以将这些流程活动定义到主流程中，但是使用内嵌子流程可以为某部分流程活动添加特定的事件范
     * 围。内嵌子流程的应用场景主要有以下两种。
     * <ol>
     *     <li>
     *         <b>用于分层建模：</b>在常见的建模工具中，都允许折叠子流程以隐藏子流程的所有细节，从而展示业务流程的高层端到端总览。
     *     </li>
     *     <li>
     *         <b>为事件创建新的作用域：</b>在子流程执行过程中抛出的事件可以通过子流程边界上的边界事件捕获，事件所创建的作用域只局限在子流程内。
     *     </li>
     * </ol>
     *  &emsp;&emsp;内嵌子流程表示为圆角矩形，它有两种形态：如果子流程是折叠的，则在底部中间带有加号标记，不展示内部细节.如果子流程是展开的，子
     * 流程内部的元素将显示在子流程边界内.其定义格式如下所示：
     * <pre>
     *     {@code
     *          <process id="mainProcess" name="主流程">
     *              <startEvent id="startEvent1"/>
     *              <subProcess id="subProcess">
     *                  <startEvent id="subProcessStart" />
     *                  <!-- 此处省略子流程其他元素定义 -->
     *                  <endEvent id="subProcessEnd" />
     *              </subProcess>
     *           </process>
     *     }
     * </pre>
     *  </p>
     *  <h3>案例演示</h3>
     *  <p>
     *      该流程为贷款申请流程。贷款申请业务包含贷款申请、贷款额度审批和发放贷款3个阶段，在流程实现中“贷款申请”和“发放贷款”阶段使用用户任务实现，而
     * “贷款额度审批”阶段使用内嵌子流程实现。同时，子流程边界上加入定时器边界事件，如果定时器触发前子流程结束，则主流程流转到“发放贷款”节点；如果定时器
     * 触发时子流程还没有结束，则直接流转到结束节点，整个流程结束。<br/>
     *      <b>流程定义文件：</b>{@link flow/subProcessProcess.bpmn20.xml}<br/>
     *      <b>流程预览图：</b><br/>
     *      <img src="https://cdn4.winhlb.com/2025/05/20/682be4f7108f0.png" />
     *  </p>
     *  <p>
     *    <b style="color:red;">注意事项：</b>在使用子流程时要考虑以下限制。
     *    <ol>
     *        <li>子流程有且只能有一个空开始事件，不允许有其他类型的开始事件。</li>
     *        <li>子流程至少有一个结束事件。需要注意的是，<u>在BPMN 2.0规范中允许子流程没有启动与结束事件，但是当前Flowable并不支持。</u></li>
     *        <li>顺序流不能跨越子流程边界，子流程中顺序流不能直接输出流到子流程之外的活动上。若有需要，可以用边界事件替代。</li>
     *    </ol>
     *  </p>
     **/
    @Test
    public void test() throws InterruptedException {
        Deployment deploy = repositoryService.createDeployment()
                .name("内嵌子流程-演示案例")
                .addClasspathResource("flow/subProcessProcess.bpmn20.xml")
                .deploy();
        log.info("部署流程定义：{}", deploy.getId());
        ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploy.getId())
                .singleResult();
        log.info("流程定义编号：{}", procDef.getId());
        //设置流程变量
        Map<String, Object> varMap1 = MapUtil.builder(new HashMap<String, Object>())
                .put("lender", "张三")
                .build();
        //启动流程实例
        ProcessInstance mainProcessInstance = runtimeService.startProcessInstanceById(procDef.getId(), varMap1);

        ExecutionQuery executionQuery = runtimeService.createExecutionQuery()
                .processInstanceId(mainProcessInstance.getId());
        //查询执行实例
        List<Execution> executionList1 = executionQuery.list();
        log.info("主流程发起后，执行实例数为{}，分别为：{}", executionList1.size(),
                JSON.toJSONString(executionList1, executionFilter));
        //查询主流程的流程变量
        Map<String, Object> mainProcessVarMap1 = runtimeService.getVariables(mainProcessInstance.getId());
        log.info("主流程的流程变量为：{}", mainProcessVarMap1);
        TaskQuery taskQuery = taskService.createTaskQuery()
                .processInstanceId(mainProcessInstance.getId());
        //查询主流程第一个任务
        Task firstTaskOfMainProcess = taskQuery.singleResult();
        log.info("主流程发起后，流程当前所在用户任务为：{}", firstTaskOfMainProcess.getName());
        //完成主流程第一个任务
        taskService.complete(firstTaskOfMainProcess.getId());
        log.info("完成用户任务：{}，启动子流程", firstTaskOfMainProcess.getName());
        //查询执行实例
        List<Execution> executionList2 = executionQuery.list();
        log.info("子流程发起后，执行实例数为{}，分别为：{}", executionList2.size(),
                JSON.toJSONString(executionList2, executionFilter));
        //查询子流程第一个任务
        Task firstTaskOfSubProcess = taskQuery.singleResult();
        log.info("子流程发起后，流程当前所在用户任务为：{}", firstTaskOfSubProcess.getName());
        //查询子流程的流程变量
        Map<String, Object> subProcessVarMap1 = runtimeService
                .getVariables(firstTaskOfSubProcess.getProcessInstanceId());
        log.info("子流程的流程变量为：{}", subProcessVarMap1);
        //设置子流程的流程变量
        Map<String, Object> varMap2 = MapUtil.builder(new HashMap<String, Object>())
                .put("loanAmount", 10000000)
                        .build();
        //完成子流程第一个任务
        taskService.complete(firstTaskOfSubProcess.getId(), varMap2);
        log.info("完成用户任务：{}", firstTaskOfSubProcess.getName());
        //查询子流程第一个任务
        Task secondTaskOfSubProcess = taskQuery.singleResult();
        log.info("完成用户任务：{}后，流程当前所在用户任务为：{}",
                firstTaskOfSubProcess.getName(), secondTaskOfSubProcess.getName());
        Map<String, Object> mainProcessVarMap2 = runtimeService
                .getVariables(mainProcessInstance.getId());
        log.info("主流程的流程变量为：{}", mainProcessVarMap2);
        //查询定时器任务
        List<Job> jobs = managementService.createTimerJobQuery().list();
        log.info("定时器任务有：{}", JSON.toJSONString(jobs, jobFilter));
        log.info("暂停90秒...");
        //暂停90秒,触发边界定时事件，流程结束
        Thread.sleep(90 * 1000);
        //查询执行实例
        List<Execution> executionList4 = executionQuery.list();
        log.info("主流程结束后，执行实例数为{}，执行实例信息为：{}", executionList4.size(),
                JSON.toJSONString(executionList4, executionFilter));

        /**
         *  演示结果：
         *  2025-05-20 10:19:01.267  INFO 51708 --- [           main] c.b.n.f.e.c.inline.InlineChildFlowTest   : 部署流程定义：cabacad9-3520-11f0-a9ed-50ebf6ec8f6d
         * 2025-05-20 10:19:01.271  INFO 51708 --- [           main] c.b.n.f.e.c.inline.InlineChildFlowTest   : 流程定义编号：subProcessProcess:1:cb2c2a4c-3520-11f0-a9ed-50ebf6ec8f6d
         * 2025-05-20 10:19:01.696  INFO 51708 --- [           main] c.b.n.f.e.c.inline.InlineChildFlowTest   : 主流程发起后，执行实例数为2，分别为：[{"id":"cb3292ed-3520-11f0-a9ed-50ebf6ec8f6d","processDefinitionId":"subProcessProcess:1:cb2c2a4c-3520-11f0-a9ed-50ebf6ec8f6d","processInstanceId":"cb3292ed-3520-11f0-a9ed-50ebf6ec8f6d","rootProcessInstanceId":"cb3292ed-3520-11f0-a9ed-50ebf6ec8f6d","scope":true},{"activityId":"firstTaskOfMainProcess","id":"cb33081f-3520-11f0-a9ed-50ebf6ec8f6d","parentId":"cb3292ed-3520-11f0-a9ed-50ebf6ec8f6d","processDefinitionId":"subProcessProcess:1:cb2c2a4c-3520-11f0-a9ed-50ebf6ec8f6d","processInstanceId":"cb3292ed-3520-11f0-a9ed-50ebf6ec8f6d","rootProcessInstanceId":"cb3292ed-3520-11f0-a9ed-50ebf6ec8f6d","scope":false}]
         * 2025-05-20 10:19:01.724  INFO 51708 --- [           main] c.b.n.f.e.c.inline.InlineChildFlowTest   : 主流程的流程变量为：{lender=张三}
         * 2025-05-20 10:19:01.738  INFO 51708 --- [           main] c.b.n.f.e.c.inline.InlineChildFlowTest   : 主流程发起后，流程当前所在用户任务为：贷款申请
         * 2025-05-20 10:19:01.824  INFO 51708 --- [           main] c.b.n.f.e.c.inline.InlineChildFlowTest   : 完成用户任务：贷款申请，启动子流程
         * 2025-05-20 10:19:01.827  INFO 51708 --- [           main] c.b.n.f.e.c.inline.InlineChildFlowTest   : 子流程发起后，执行实例数为4，分别为：[{"id":"cb3292ed-3520-11f0-a9ed-50ebf6ec8f6d","processDefinitionId":"subProcessProcess:1:cb2c2a4c-3520-11f0-a9ed-50ebf6ec8f6d","processInstanceId":"cb3292ed-3520-11f0-a9ed-50ebf6ec8f6d","rootProcessInstanceId":"cb3292ed-3520-11f0-a9ed-50ebf6ec8f6d","scope":true},{"activityId":"subProcess","id":"cb7b5bc5-3520-11f0-a9ed-50ebf6ec8f6d","parentId":"cb3292ed-3520-11f0-a9ed-50ebf6ec8f6d","processDefinitionId":"subProcessProcess:1:cb2c2a4c-3520-11f0-a9ed-50ebf6ec8f6d","processInstanceId":"cb3292ed-3520-11f0-a9ed-50ebf6ec8f6d","rootProcessInstanceId":"cb3292ed-3520-11f0-a9ed-50ebf6ec8f6d","scope":true},{"activityId":"boundaryEventOfMainProcess","id":"cb7b82d7-3520-11f0-a9ed-50ebf6ec8f6d","parentId":"cb7b5bc5-3520-11f0-a9ed-50ebf6ec8f6d","processDefinitionId":"subProcessProcess:1:cb2c2a4c-3520-11f0-a9ed-50ebf6ec8f6d","processInstanceId":"cb3292ed-3520-11f0-a9ed-50ebf6ec8f6d","rootProcessInstanceId":"cb3292ed-3520-11f0-a9ed-50ebf6ec8f6d","scope":false},{"activityId":"firstTaskOfSubProcess","id":"cb7b82d9-3520-11f0-a9ed-50ebf6ec8f6d","parentId":"cb7b5bc5-3520-11f0-a9ed-50ebf6ec8f6d","processDefinitionId":"subProcessProcess:1:cb2c2a4c-3520-11f0-a9ed-50ebf6ec8f6d","processInstanceId":"cb3292ed-3520-11f0-a9ed-50ebf6ec8f6d","rootProcessInstanceId":"cb3292ed-3520-11f0-a9ed-50ebf6ec8f6d","scope":false}]
         * 2025-05-20 10:19:01.828  INFO 51708 --- [           main] c.b.n.f.e.c.inline.InlineChildFlowTest   : 子流程发起后，流程当前所在用户任务为：审批贷款额度
         * 2025-05-20 10:19:01.830  INFO 51708 --- [           main] c.b.n.f.e.c.inline.InlineChildFlowTest   : 子流程的流程变量为：{lender=张三}
         * 2025-05-20 10:19:01.862  INFO 51708 --- [           main] c.b.n.f.e.c.inline.InlineChildFlowTest   : 完成用户任务：审批贷款额度
         * 2025-05-20 10:19:01.863  INFO 51708 --- [           main] c.b.n.f.e.c.inline.InlineChildFlowTest   : 完成用户任务：审批贷款额度后，流程当前所在用户任务为：复核贷款额度
         * 2025-05-20 10:19:01.866  INFO 51708 --- [           main] c.b.n.f.e.c.inline.InlineChildFlowTest   : 主流程的流程变量为：{lender=张三, loanAmount=10000000}
         * 2025-05-20 10:19:01.876  INFO 51708 --- [           main] c.b.n.f.e.c.inline.InlineChildFlowTest   : 定时器任务有：[{"duedate":"2025-05-20 10:20:01.79","executionId":"cb7b82d7-3520-11f0-a9ed-50ebf6ec8f6d","id":"cb80b2fb-3520-11f0-a9ed-50ebf6ec8f6d","jobHandlerType":"trigger-timer","jobType":"timer","processDefinitionId":"subProcessProcess:1:cb2c2a4c-3520-11f0-a9ed-50ebf6ec8f6d","processInstanceId":"cb3292ed-3520-11f0-a9ed-50ebf6ec8f6d","retries":3}]
         * 2025-05-20 10:19:01.876  INFO 51708 --- [           main] c.b.n.f.e.c.inline.InlineChildFlowTest   : 暂停90秒...
         * 2025-05-20 10:20:31.889  INFO 51708 --- [           main] c.b.n.f.e.c.inline.InlineChildFlowTest   : 主流程结束后，执行实例数为0，执行实例信息为：[]
         **/
    }

    SimplePropertyPreFilter executionFilter = new SimplePropertyPreFilter(Execution.class,
            "id", "parentId", "processInstanceId", "processDefinitionId",
            "rootProcessInstanceId", "scope", "activityId", "businessKey");
    SimplePropertyPreFilter jobFilter = new SimplePropertyPreFilter(Job.class,
            "id", "executionId", "duedate", "jobHandlerType", "jobType",
            "processDefinitionId", "processInstanceId", "retries");
}
